// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/speech/speech_recognition_dispatcher_host.h"

#include "base/bind.h"
#include "base/command_line.h"
#include "base/lazy_instance.h"
#include "content/browser/browser_plugin/browser_plugin_guest.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/speech/speech_recognition_manager_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/speech_recognition_messages.h"
#include "content/public/browser/speech_recognition_manager_delegate.h"
#include "content/public/browser/speech_recognition_session_config.h"
#include "content/public/browser/speech_recognition_session_context.h"
#include "content/public/common/content_switches.h"

namespace content {

SpeechRecognitionDispatcherHost::SpeechRecognitionDispatcherHost(
    int render_process_id,
    net::URLRequestContextGetter* context_getter)
    : BrowserMessageFilter(SpeechRecognitionMsgStart)
    , render_process_id_(render_process_id)
    , context_getter_(context_getter)
    , weak_factory_(this)
{
    // Do not add any non-trivial initialization here, instead do it lazily when
    // required (e.g. see the method |SpeechRecognitionManager::GetInstance()|) or
    // add an Init() method.
}

SpeechRecognitionDispatcherHost::~SpeechRecognitionDispatcherHost()
{
    SpeechRecognitionManager::GetInstance()->AbortAllSessionsForRenderProcess(
        render_process_id_);
}

base::WeakPtr<SpeechRecognitionDispatcherHost>
SpeechRecognitionDispatcherHost::AsWeakPtr()
{
    return weak_factory_.GetWeakPtr();
}

void SpeechRecognitionDispatcherHost::OnDestruct() const
{
    BrowserThread::DeleteOnIOThread::Destruct(this);
}

bool SpeechRecognitionDispatcherHost::OnMessageReceived(
    const IPC::Message& message)
{
    bool handled = true;
    IPC_BEGIN_MESSAGE_MAP(SpeechRecognitionDispatcherHost, message)
    IPC_MESSAGE_HANDLER(SpeechRecognitionHostMsg_StartRequest,
        OnStartRequest)
    IPC_MESSAGE_HANDLER(SpeechRecognitionHostMsg_AbortRequest,
        OnAbortRequest)
    IPC_MESSAGE_HANDLER(SpeechRecognitionHostMsg_StopCaptureRequest,
        OnStopCaptureRequest)
    IPC_MESSAGE_HANDLER(SpeechRecognitionHostMsg_AbortAllRequests,
        OnAbortAllRequests)
    IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()
    return handled;
}

void SpeechRecognitionDispatcherHost::OverrideThreadForMessage(
    const IPC::Message& message,
    BrowserThread::ID* thread)
{
    if (message.type() == SpeechRecognitionHostMsg_StartRequest::ID)
        *thread = BrowserThread::UI;
}

void SpeechRecognitionDispatcherHost::OnChannelClosing()
{
    weak_factory_.InvalidateWeakPtrs();
}

void SpeechRecognitionDispatcherHost::OnStartRequest(
    const SpeechRecognitionHostMsg_StartRequest_Params& params)
{
    SpeechRecognitionHostMsg_StartRequest_Params input_params(params);

    // Check that the origin specified by the renderer process is one
    // that it is allowed to access.
    if (params.origin_url != "null" && !ChildProcessSecurityPolicyImpl::GetInstance()->CanRequestURL(render_process_id_, GURL(params.origin_url))) {
        LOG(ERROR) << "SRDH::OnStartRequest, disallowed origin: "
                   << params.origin_url;
        return;
    }

    int embedder_render_process_id = 0;
    int embedder_render_view_id = MSG_ROUTING_NONE;
    RenderViewHostImpl* render_view_host = RenderViewHostImpl::FromID(render_process_id_, params.render_view_id);
    if (!render_view_host) {
        // RVH can be null if the tab was closed while continuous mode speech
        // recognition was running. This seems to happen on mac.
        LOG(WARNING) << "SRDH::OnStartRequest, RenderViewHost does not exist";
        return;
    }

    WebContentsImpl* web_contents = static_cast<WebContentsImpl*>(
        WebContents::FromRenderViewHost(render_view_host));
    WebContentsImpl* outer_web_contents = web_contents->GetOuterWebContents();
    if (outer_web_contents) {
        // If the speech API request was from an inner WebContents or a guest, save
        // the context of the outer WebContents or the embedder since we will use it
        // to decide permission.
        embedder_render_process_id = outer_web_contents->GetRenderProcessHost()->GetID();
        DCHECK_NE(embedder_render_process_id, 0);
        embedder_render_view_id = outer_web_contents->GetRenderViewHost()->GetRoutingID();
        DCHECK_NE(embedder_render_view_id, MSG_ROUTING_NONE);
    }

    // TODO(lazyboy): Check if filter_profanities should use |render_process_id|
    // instead of |render_process_id_|.
    bool filter_profanities = SpeechRecognitionManagerImpl::GetInstance() && SpeechRecognitionManagerImpl::GetInstance()->delegate() && SpeechRecognitionManagerImpl::GetInstance()->delegate()->FilterProfanities(render_process_id_);

    // TODO(miu): This is a hack to allow SpeechRecognition to operate with the
    // MediaStreamManager, which partitions requests per RenderFrame, not per
    // RenderView.  http://crbug.com/390749
    const int params_render_frame_id = render_view_host ? render_view_host->GetMainFrame()->GetRoutingID() : MSG_ROUTING_NONE;

    BrowserThread::PostTask(
        BrowserThread::IO,
        FROM_HERE,
        base::Bind(&SpeechRecognitionDispatcherHost::OnStartRequestOnIO,
            this,
            embedder_render_process_id,
            embedder_render_view_id,
            input_params,
            params_render_frame_id,
            filter_profanities));
}

void SpeechRecognitionDispatcherHost::OnStartRequestOnIO(
    int embedder_render_process_id,
    int embedder_render_view_id,
    const SpeechRecognitionHostMsg_StartRequest_Params& params,
    int params_render_frame_id,
    bool filter_profanities)
{
    SpeechRecognitionSessionContext context;
    context.context_name = params.origin_url;
    context.render_process_id = render_process_id_;
    context.render_view_id = params.render_view_id;
    context.render_frame_id = params_render_frame_id;
    context.embedder_render_process_id = embedder_render_process_id;
    context.embedder_render_view_id = embedder_render_view_id;
    if (embedder_render_process_id)
        context.guest_render_view_id = params.render_view_id;
    context.request_id = params.request_id;

    SpeechRecognitionSessionConfig config;
    config.language = params.language;
    config.grammars = params.grammars;
    config.max_hypotheses = params.max_hypotheses;
    config.origin_url = params.origin_url;
    config.initial_context = context;
    config.url_request_context_getter = context_getter_.get();
    config.filter_profanities = filter_profanities;
    config.continuous = params.continuous;
    config.interim_results = params.interim_results;
    config.event_listener = AsWeakPtr();

    int session_id = SpeechRecognitionManager::GetInstance()->CreateSession(
        config);
    DCHECK_NE(session_id, SpeechRecognitionManager::kSessionIDInvalid);
    SpeechRecognitionManager::GetInstance()->StartSession(session_id);
}

void SpeechRecognitionDispatcherHost::OnAbortRequest(int render_view_id,
    int request_id)
{
    int session_id = SpeechRecognitionManager::GetInstance()->GetSession(
        render_process_id_, render_view_id, request_id);

    // The renderer might provide an invalid |request_id| if the session was not
    // started as expected, e.g., due to unsatisfied security requirements.
    if (session_id != SpeechRecognitionManager::kSessionIDInvalid)
        SpeechRecognitionManager::GetInstance()->AbortSession(session_id);
}

void SpeechRecognitionDispatcherHost::OnAbortAllRequests(int render_view_id)
{
    SpeechRecognitionManager::GetInstance()->AbortAllSessionsForRenderView(
        render_process_id_, render_view_id);
}

void SpeechRecognitionDispatcherHost::OnStopCaptureRequest(
    int render_view_id, int request_id)
{
    int session_id = SpeechRecognitionManager::GetInstance()->GetSession(
        render_process_id_, render_view_id, request_id);

    // The renderer might provide an invalid |request_id| if the session was not
    // started as expected, e.g., due to unsatisfied security requirements.
    if (session_id != SpeechRecognitionManager::kSessionIDInvalid) {
        SpeechRecognitionManager::GetInstance()->StopAudioCaptureForSession(
            session_id);
    }
}

// -------- SpeechRecognitionEventListener interface implementation -----------

void SpeechRecognitionDispatcherHost::OnRecognitionStart(int session_id)
{
    const SpeechRecognitionSessionContext& context = SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
    Send(new SpeechRecognitionMsg_Started(context.render_view_id,
        context.request_id));
}

void SpeechRecognitionDispatcherHost::OnAudioStart(int session_id)
{
    const SpeechRecognitionSessionContext& context = SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
    Send(new SpeechRecognitionMsg_AudioStarted(context.render_view_id,
        context.request_id));
}

void SpeechRecognitionDispatcherHost::OnSoundStart(int session_id)
{
    const SpeechRecognitionSessionContext& context = SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
    Send(new SpeechRecognitionMsg_SoundStarted(context.render_view_id,
        context.request_id));
}

void SpeechRecognitionDispatcherHost::OnSoundEnd(int session_id)
{
    const SpeechRecognitionSessionContext& context = SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
    Send(new SpeechRecognitionMsg_SoundEnded(context.render_view_id,
        context.request_id));
}

void SpeechRecognitionDispatcherHost::OnAudioEnd(int session_id)
{
    const SpeechRecognitionSessionContext& context = SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
    Send(new SpeechRecognitionMsg_AudioEnded(context.render_view_id,
        context.request_id));
}

void SpeechRecognitionDispatcherHost::OnRecognitionEnd(int session_id)
{
    const SpeechRecognitionSessionContext& context = SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
    Send(new SpeechRecognitionMsg_Ended(context.render_view_id,
        context.request_id));
}

void SpeechRecognitionDispatcherHost::OnRecognitionResults(
    int session_id,
    const SpeechRecognitionResults& results)
{
    const SpeechRecognitionSessionContext& context = SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
    Send(new SpeechRecognitionMsg_ResultRetrieved(context.render_view_id,
        context.request_id,
        results));
}

void SpeechRecognitionDispatcherHost::OnRecognitionError(
    int session_id,
    const SpeechRecognitionError& error)
{
    const SpeechRecognitionSessionContext& context = SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
    Send(new SpeechRecognitionMsg_ErrorOccurred(context.render_view_id,
        context.request_id,
        error));
}

// The events below are currently not used by speech JS APIs implementation.
void SpeechRecognitionDispatcherHost::OnAudioLevelsChange(int session_id,
    float volume,
    float noise_volume)
{
}

void SpeechRecognitionDispatcherHost::OnEnvironmentEstimationComplete(
    int session_id)
{
}

} // namespace content
